<!DOCTYPE html>
<html lang="zh">
  
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>I Love You</title>
    <style>html, body { height: 100%; margin: 0; padding: 0; overflow: hidden; background: rgb(0, 0, 0); } canvas { position: absolute; width: 100%; height: 100%; top: 0; left: 0; } #child { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); } h4 { font-family: "STKaiti", serif; font-size: 40px; color: #f584b7; text-align: center; }</style></head>
  
  <body>
    <div id="child">
      <h4>💗我永远为你着迷</h4></div>
    <canvas id="pinkboard"></canvas>
    <script>// Particle System Settings        
      const settings = {
        particles: {
          length: 500,
          duration: 2,
          velocity: 100,
          effect: -0.75,
          size: 30
        }
      };
      // Polyfill for requestAnimationFrame        
      (function() {
        let lastTime = 0;
        const vendors = ['ms', 'moz', 'webkit', 'o'];
        for (let x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
          window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
          window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
        }
        if (!window.requestAnimationFrame) {
          window.requestAnimationFrame = function(callback) {
            const now = new Date().getTime();
            const timeToCall = Math.max(0, 16 - (now - lastTime));
            const id = window.setTimeout(() =>callback(now + timeToCall), timeToCall);
            lastTime = now + timeToCall;
            return id;
          };
        }
        if (!window.cancelAnimationFrame) {
          window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
          };
        }
      })();
      // Point class        
      class Point {
        constructor(x = 0, y = 0) {
          this.x = x;
          this.y = y;
        }
        clone() {
          return new Point(this.x, this.y);
        };
        length(length) {
          if (length === undefined) return Math.sqrt(this.x **2 + this.y **2);
          this.normalize();
          this.x *= length;
          this.y *= length;
          return this;
        };
        normalize() {
          const len = this.length();
          this.x /= len;
          this.y /= len;
          return this;
        }
      }
      // Particle class        
      class Particle {
        constructor() {
          this.position = new Point();
          this.velocity = new Point();
          this.acceleration = new Point();
          this.age = 0;
        }
        initialize(x, y, dx, dy) {
          this.position.x = x;
          this.position.y = y;
          this.velocity.x = dx;
          this.velocity.y = dy;
          this.acceleration.x = dx * settings.particles.effect;
          this.acceleration.y = dy * settings.particles.effect;
          this.age = 0;
        }
        update(deltaTime) {
          this.position.x += this.velocity.x * deltaTime;
          this.position.y += this.velocity.y * deltaTime;
          this.velocity.x += this.acceleration.x * deltaTime;
          this.velocity.y += this.acceleration.y * deltaTime;
          this.age += deltaTime;
        }
        draw(context, image) {
          const ease = t =>--t * t * t + 1;
          const size = image.width * ease(this.age / settings.particles.duration);
          context.globalAlpha = 1 - this.age / settings.particles.duration;
          context.drawImage(image, this.position.x - size / 2, this.position.y - size / 2, size, size);
        }
      }
      // ParticlePool class        
      class ParticlePool {
        constructor(length) {
          this.particles = Array.from({
            length
          },
          () =>new Particle());
          this.firstActive = 0;
          this.firstFree = 0;
        }
        add(x, y, dx, dy) {
          this.particles[this.firstFree].initialize(x, y, dx, dy);
          this.firstFree = (this.firstFree + 1) % this.particles.length;
          if (this.firstActive === this.firstFree) {
            this.firstActive = (this.firstActive + 1) % this.particles.length;
          }
        }
        update(deltaTime) {
          const length = this.particles.length;
          for (let i = this.firstActive; i !== this.firstFree; i = (i + 1) % length) {
            this.particles[i].update(deltaTime);
          }
          while (this.particles[this.firstActive].age >= settings.particles.duration && this.firstActive !== this.firstFree) {
            this.firstActive = (this.firstActive + 1) % length;
          }
        }
        draw(context, image) {
          const length = this.particles.length;
          for (let i = this.firstActive; i !== this.firstFree; i = (i + 1) % length) {
            this.particles[i].draw(context, image);
          }
        }
      }
      // Main rendering logic        
      (function(canvas) {
        const context = canvas.getContext("2d");
        const particles = new ParticlePool(settings.particles.length);
        const particleRate = settings.particles.length / settings.particles.duration;
        let time;
        function pointOnHeart(t) {
          return new Point(160 * Math.pow(Math.sin(t), 3), 130 * Math.cos(t) - 50 * Math.cos(2 * t) - 20 * Math.cos(3 * t) - 10 * Math.cos(4 * t) + 25);
        }
        const image = (function() {
          const canvas = document.createElement("canvas");
          const context = canvas.getContext("2d");
          canvas.width = settings.particles.size;
          canvas.height = settings.particles.size;
          function to(t) {
            const point = pointOnHeart(t);
            point.x = settings.particles.size / 2 + (point.x * settings.particles.size) / 350;
            point.y = settings.particles.size / 2 - (point.y * settings.particles.size) / 350;
            return point;
          }
          context.beginPath();
          let t = -Math.PI;
          let point = to(t);
          context.moveTo(point.x, point.y);
          while (t < Math.PI) {
            t += 0.01;
            point = to(t);
            context.lineTo(point.x, point.y);
          }
          context.closePath();
          context.fillStyle = "#ea80b0";
          context.fill();
          const image = new Image();
          image.src = canvas.toDataURL();
          return image;
        })();
        function render() {
          requestAnimationFrame(render);
          const newTime = Date.now() / 1000;
          const deltaTime = newTime - (time || newTime);
          time = newTime;
          context.clearRect(0, 0, canvas.width, canvas.height);
          const amount = particleRate * deltaTime;
          for (let i = 0; i < amount; i++) {
            const pos = pointOnHeart(Math.PI - 2 * Math.PI * Math.random());
            const dir = pos.clone().length(settings.particles.velocity);
            particles.add(canvas.width / 2 + pos.x, canvas.height / 2 - pos.y, dir.x, -dir.y);
          }
          particles.update(deltaTime);
          particles.draw(context, image);
        }
        function onResize() {
          canvas.width = canvas.clientWidth;
          canvas.height = canvas.clientHeight;
        }
        window.onresize = onResize;
        setTimeout(() =>{
          onResize();
          render();
        },
        10);
      })(document.getElementById("pinkboard"));</script>
  </body>

</html>